package com.liycloud.sys.config;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.liycloud.sys.api.entity.SysRouteConf;
import com.liycloud.sys.service.SysRouteConfService;
import com.liycloud.common.core.constant.CacheConstants;
import com.liycloud.common.gateway.support.DynamicRouteInitEvent;
import com.liycloud.common.gateway.vo.RouteDefinitionVo;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;

import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.scheduling.annotation.Async;

import java.net.URI;
import java.util.List;

/**
 * @author lengleng
 * @date 2018/10/31
 * <p>
 *
 *  这个类负责 容器启动时加载数据库(MySQL) 中的配置信息 到 Redis
 *
 *  初始化, 网关的路由配置, 从MySQL的 sys_route_conf 表中读取路由信息 保存到Redis
 *
 *
 */

@Configuration
@Slf4j
@AllArgsConstructor
public class DynamicRouteInitRunner {

	private final RedisTemplate redisTemplate;

	// 网关路由信息 CRUD 服务
	private final SysRouteConfService routeConfService;


	/**
	 * 初始化, 网关的路由配置, 从MySQL的 sys_route_conf 表中读取
	 *
	 */
	@Async
	@Order        // 监听事件: 1.web容器初始化成功后  2.动态路由初始化
	@EventListener({ WebServerInitializedEvent.class, DynamicRouteInitEvent.class })
	public void initRoute() {

		// 先删除 gl:gateway_route_key  路由
		Boolean result = redisTemplate.delete(CacheConstants.ROUTE_KEY);
		log.info("初始化网关路由 {} ", result);


		// 获取 SysRouteConf 定义类列表, SysRouteConf 网关路由管理模块的实体类, 可以使用表单CRUD 配置网关路由
		// SysRouteConf  对应数据库表:  sys_route_conf
		List<SysRouteConf> sysRouteConfList = routeConfService.list();


		sysRouteConfList.forEach(route -> {

			// 框架网关路由定义类
			RouteDefinitionVo vo = new RouteDefinitionVo();

			vo.setRouteName(route.getRouteName());
			vo.setId(route.getRouteId());
			vo.setUri(URI.create(route.getUri()));
			vo.setOrder(route.getOrder());

			//过滤器 json 数组
			JSONArray filterObj = JSONUtil.parseArray(route.getFilters());
			vo.setFilters(filterObj.toList(FilterDefinition.class));

			// 断言 json 数组
			JSONArray predicateObj = JSONUtil.parseArray(route.getPredicates());
			vo.setPredicates(predicateObj.toList(PredicateDefinition.class));

			log.info("加载路由ID：{}, 网关路由定义对象: {}", route.getRouteId(), vo);

			// 设置序列化器
			redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class));

			// 保存到 redis
			redisTemplate.opsForHash().put(CacheConstants.ROUTE_KEY, route.getRouteId(), vo);
		});


		log.info("初始化网关路由结束 ");
	}

	/**
	 * redis 监听配置,监听 gateway_redis_route_reload_topic,重新加载Redis
	 * @param redisConnectionFactory redis 配置
	 * @return
	 */
	@Bean
	public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) {
		RedisMessageListenerContainer container = new RedisMessageListenerContainer();
		container.setConnectionFactory(redisConnectionFactory);
		container.addMessageListener((message, bytes) -> {
			log.warn("接收到重新Redis 重新加载路由事件");
			initRoute();
		}, new ChannelTopic(CacheConstants.ROUTE_REDIS_RELOAD_TOPIC));
		return container;
	}

}
